Skip to content

Native player main thread 3-7 ms faster on Midway: held batch decision + render thread on by default (PRD-613) - #577

Merged
jonit-dev merged 10 commits into
developfrom
prd-613-render-thread
Oct 27, 2026
Merged

jonit-dev merged 10 commits into
developfrom
prd-613-render-thread

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What is this PR about?

The native desktop player's main (game) thread does less work per frame. Two changes do this:

  1. Batching holds its projection decision. RenderDatabase::batch re-ran projection::decide on every frame, with a full scene walk and regroup, only to read the skinned rigs' verdicts. The decision now holds while the scene structure (hierarchyVersion + structureStamp) and every rig's lane key (lane reason, similarity, mirror sign) hold. The 60-frame cadence still re-judges it. structureStamp moved into projection/plan.h, so hostProjectionReport and the renderer share one rule.
  2. The render thread is on by default. Stage 1 (encode on a second thread) came in through feat(core): PRD-624 release the native engine as the default (PRD-573 v1) #493. TN_RENDER_THREAD=0 turns it off, and a GPU timing run keeps it off.

Also in this PR:

Why does it matter?

Midway (desktop, matched CPU, 3 alternating pairs against today's develop path) runs 3.4 to 7.5 ms less main-thread CPU per frame at takeoff, cockpit and rear gunner. Instructions per frame fall from 40-48 M to 29-34 M (about −27%). Parity PASS in every run. The visual judge returns NEUTRAL at all three poses (sheets in the PR comment).

Done (PRD-613, prd:100%)

  • Order ctest: Release and TSan (Mesa lavapipe) 4/4, with an alias control that goes red.
  • Stage 1 lever (go/no-go): instructions drop in every on-run. ⚠️ The CPU-ms clause is not shown, because the 0.6 ms effect is smaller than host-load noise (PRD Decisions 2026-10-27).
  • Batch decision parity ctest native_engine_skinned_projecting_cadence: red then green. ctest -L native-engine 516/517: the only red is target_graph, from this checkout's UI-overlay configure.
  • Stage 2 lever, Three pairs, Steady state has no fallback, Pixels unchanged.
  • On by default: a red flight on the old binary, a green flight on the new one.

Still open, outside this PR

  • PRD-610 AC-16 stays open for the owner. Cull and project stay on the main thread, because the batch decision is held instead of moved.
  • The takeoff pilot pose and the rear-gunner HUD radar vary between runs of the same arm. Some state there is sampled off the fixed-step clock.

@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Oct 27, 2026
…PRD-743 surface)

PRD-743 added IAssetLoader.cache, prefetch and startBackgroundFill, and the
playtest bridge's state sample reads cache.enabled. The native player's
loader had none of them, so every native playtest observation threw
TN_INSPECT_GAME_BRIDGE (reading 'enabled'), and a game passing
assets.cache or assets.backgroundFill threw TN_NATIVE_ASSET_OPTIONS_UNSUPPORTED.
The native loader now reports the cache as disabled with core's native
reason, and prefetch and the fill resolve at once.

Red-green: native_engine_player_imports red on the options refusal, green after.
… structure does

RenderDatabase::batch judged a projecting projection decision again every
frame: a full scene walk, content classes and the group maps, only to read
the rigs' verdicts. On Midway that was about 11% of main-thread samples at
cockpit and rear gunner. The decision now holds while hierarchyVersion and
structureStamp (moved from the bindings into projection/plan.h, shared with
hostProjectionReport) hold, and while every rig's lane key (lane reason,
similarity, mirror sign) holds; the 60-frame cadence still re-judges it.
TN_BATCH_DECISION_CACHE=0 (diagnostic) restores the old cadence for paired arms.

ctest: skinned_projecting_cadence red then green; skinned_crowd_cpu caught the
rig-scale and material cases; native-engine label 516/517 (target_graph is the
UI-overlay configure).
Phase 3 passed on Midway at 99ad82d: main-thread CPU per frame falls by
3.4-7.5 ms per pose in three alternating pairs (render thread plus the held
batch decision against today's develop path), parity PASS in every run, no
steady-state fallback, visual judge NEUTRAL at all three poses.

TN_RENDER_THREAD=0 turns it off; a GPU timing run keeps it off, since it has
no synchronized timing snapshot (TN_RENDER_THREAD=1 with GPU timing still
fails closed). Red: a Midway flight with the variable unset on the binary
before this change reports no renderThreadCpuMs.
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Pixels unchanged: visual judge NEUTRAL (PRD-613 Phase 3)

Panels, left to right: BEFORE A1 and BEFORE A2 (TN_RENDER_THREAD=0, decision re-judged every frame, as on develop), then AFTER B (render thread plus the held batch decision). Engine 99ad82d99, fixed-step clock, desktop player, RTX 2080.

Takeoff
takeoff

Cockpit
cockpit

Rear gunner
rear gunner

Verdict (fresh read-only Opus subagent, criteria set before judging): NEUTRAL at all three poses. No missing objects and no change to pose, material, shadow or flicker.

Pixel counts over 1% grey, across 7 off-runs and 10 on-runs:

  • Cockpit: 0 in every pair.
  • Rear gunner: the HUD radar differs by 213 px in A-A pairs and in A-B pairs alike.
  • Takeoff: the pilot's head (a 22x19 region) has two states on the off arm and three on the on arm, at most 12 px.

Follow-up outside this PRD: some state at those two spots is sampled off the fixed-step clock.

dflt-green (render thread on with no flag, after the switch) reports
renderThreadCpuMs in 4,574 frame lines, fallbacks 1, parity PASS; dflt-red on
99ad82d reports none. The PRD moves to docs/PRDs/done/. PRD-610 AC-16 stays
open for the owner: cull and project stay on the main thread.
@jonit-dev jonit-dev added prd:100% — ready PRD complete, ready to merge and removed prd:25% PRD phase 1 verified labels Oct 27, 2026
@jonit-dev jonit-dev changed the title Native render thread: move render work off the game thread (PRD-613) Native player main thread 3-7 ms faster on Midway: held batch decision + render thread on by default (PRD-613) Oct 27, 2026
@jonit-dev
jonit-dev marked this pull request as ready for review October 27, 2026 11:54
@jonit-dev
jonit-dev added this pull request to the merge queue Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The merge queue stopped on supply-chain (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 27, 2026
…ranches

The merge-group secrets scan reads every fetched ref, so a verified false
positive on another open branch stops every queued PR. Same entries as
prd/temporal-upsampling bc25d08, plus the copy on pr563-followup.
@jonit-dev
jonit-dev enabled auto-merge October 27, 2026 12:56
@jonit-dev
jonit-dev added this pull request to the merge queue Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The merge queue stopped on native-platforms / Native required coverage receipt (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 27, 2026
@jonit-dev
jonit-dev added this pull request to the merge queue Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The merge queue stopped on template-nonvisual (adventure) (failure).

Fix, push, re-enqueue.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 27, 2026
@jonit-dev
jonit-dev added this pull request to the merge queue Oct 27, 2026
Merged via the queue into develop with commit fbb2b9f Oct 27, 2026
36 checks passed
@jonit-dev
jonit-dev deleted the prd-613-render-thread branch October 27, 2026 15:51
jonit-dev added a commit that referenced this pull request Oct 27, 2026
…its generated records

Resolves the four conflicts develop's #577/#579 left against this branch, and
recomputes the two generated records on the merged tree (neither parent's values
are right for it).

- .gitleaksignore: keep develop's allowlist entry for #574's kit-loop KPI field
  id (the branch had nothing there).
- core-assets.mjs: union. This lane's idempotent `dispose()` (final release must
  not empty the reusable cache) plus develop's PRD-743 report surface (`cache`,
  `prefetch`, `startBackgroundFill`) that the playtest bridge's state sample
  reads. Both sides' new player-imports checks pass against it.
- scaffold.spec.ts: recompute PRD_201_PARENT_SCAFFOLD_HASHES on the merged tree.
  The kits this lane touched (collectathon, adventure, kart, starter) are a third
  state, not either parent's; the untouched kits take develop's values.
- native-coverage-2026-08-28.md: regenerated by `native:coverage` on the merged
  tree (48 contract targets executed, 3 blocked as configured). Source digest
  22ac6e9e…; src/engine/ 83.90% against its 82.22% floor. Floor tables were
  identical on both sides.

Verified locally on the merged tree: typecheck, lint, `budgets`, `check:docs`;
unit phase 13,555 passed (17 failures are machine-only: 14 need Node 20 and pass
under v20.19.6, 1 needed the Wasm engine and 2 the browser, both green once
built); scaffold hash spec green; native C++ board green; Wasm engine 4,192,083 B
against the 4 MiB budget (2,221 B under, this lane's 2,178 under held).
Not re-run locally: the 21 runtime-native package tests that need the
`build/tn-linux/*-test` targets, which `native:build` does not build — CI's
test-native job owns them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prd:100% — ready PRD complete, ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant